package com.pie4cloud.pie.dmz.handler;

import com.alibaba.fastjson.JSONObject;
import com.pie4cloud.pie.dmz.entty.FileInfo;
import com.pie4cloud.pie.dmz.entty.TmpVo;
import com.pie4cloud.pie.dmz.entty.TransferInfo;
import com.pie4cloud.pie.dmz.entty.constant.ConstantValue;
import com.pie4cloud.pie.dmz.entty.enums.MsgTypeEnum;
import com.pie4cloud.pie.dmz.entty.netty.Message;
import com.pie4cloud.pie.dmz.util.FileUtil;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FileUtils;
import org.springframework.stereotype.Component;

import java.io.*;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.SynchronousQueue;

@Component
@ChannelHandler.Sharable
public class NettyUpDownHandler extends ChannelInboundHandlerAdapter {

	//private ChannelHandlerContext ctx;
	//private String fileName;//要下载的文件名

	//private long fileLength;//下载文件的大小
	//private TransferInfo transferInfo;
/*	@Autowired
	private NettyConnManage nettyConnManage;*/
	private ConcurrentHashMap<String,SynchronousQueue<Object>> queueMap = new ConcurrentHashMap<>();
	private ConcurrentHashMap<String,TmpVo> tmp = new ConcurrentHashMap<>();


	/**
	 * @param
	 * @param
	 * @return
	 * @throws
	 * @author fanml
	 * @date 2021/7/4 17:24
	 */
/*	public NettyClientDownloadHandler(TransferInfo transferInfo) {
		//初始化一些变量信息
		//this.fileName = fileName;
		//this.fileLength = fileLength;
		this.transferInfo=transferInfo;
		md5FileName = DigestUtils.md5Hex(transferInfo.getFileName() );
		localFile = new File(transferInfo.getTargetPath(), transferInfo.getFileName());
		if(!transferInfo.getDmzPath().endsWith("/")){
			transferInfo.setDmzPath(transferInfo.getDmzPath()+"/");
		}
		String md5LocalBlockDir = transferInfo.getDmzPath() + md5FileName;
		if (!new File(md5LocalBlockDir).exists()) {
			new File(md5LocalBlockDir).mkdirs();
		}
		//记录已下载的文件块号
		File[] files = new File(md5LocalBlockDir).listFiles();
		receiveBlock = files.length;
		if (receiveBlock > 0) {
			try {
				FileUtils.deleteDirectory(files[receiveBlock - 1]);
			} catch (IOException e) {
				e.printStackTrace();
			}
		} else {
			receiveBlock = 0;
		}
	}*/
	public SynchronousQueue<Object> sendRequest(TransferInfo transferInfo, Channel channel) throws IOException {
		TmpVo tmpVo=new TmpVo();
		transferInfo.setFilePath(transferInfo.getFilePath().replaceAll("\\\\","/"));
		transferInfo.setDmzPath(transferInfo.getDmzPath().replaceAll("\\\\","/"));
		transferInfo.setTargetPath(transferInfo.getTargetPath().replaceAll("\\\\","/"));
		tmpVo.setTransferInfo(transferInfo);
		String sessionId = UUID.randomUUID().toString().replaceAll("-", "");
		try {
			if(transferInfo.getMsgTypeEnum()==MsgTypeEnum.DOWNlOAD){
				tmpVo.setMd5FileName(DigestUtils.md5Hex(transferInfo.getFileName()) );
				tmpVo.setLocalFile(new File(transferInfo.getTargetPath(), transferInfo.getFileName()));
				if(!transferInfo.getDmzPath().endsWith("/")){
					transferInfo.setDmzPath(transferInfo.getDmzPath()+"/");
				}
				String md5LocalBlockDir = transferInfo.getDmzPath() + tmpVo.getMd5FileName();
				if (!new File(md5LocalBlockDir).exists()) {
					new File(md5LocalBlockDir).mkdirs();
				}
				//记录已下载的文件块号
				File[] files = new File(md5LocalBlockDir).listFiles();
				tmpVo.setReceiveBlock(files.length);
				if (tmpVo.getReceiveBlock() > 0) {
					try {
						FileUtils.deleteDirectory(files[tmpVo.getReceiveBlock() - 1]);
					} catch (IOException e) {
						e.printStackTrace();
					}
				} else {
					tmpVo.setReceiveBlock(0);
				}
				FileInfo fileInfo = new FileInfo(transferInfo.getFileName(), 0, 0, tmpVo.getReceiveBlock(), null);
				fileInfo.setFilePath(transferInfo.getFilePath());
				fileInfo.setDmzPath(transferInfo.getDmzPath());
				fileInfo.setTargetPath(transferInfo.getTargetPath());
				fileInfo.setFtpAddress(transferInfo.getFtpAddress());
				fileInfo.setFtpUserName(transferInfo.getFtpUserName());
				fileInfo.setFtpPwd(transferInfo.getFtpPwd());
				fileInfo.setFtpPort(transferInfo.getFtpPort());
				fileInfo.setFtpId(transferInfo.getFtpId());
				String s = JSONObject.toJSONString(fileInfo);
				Message message = new Message(sessionId, MsgTypeEnum.DOWNlOAD, s.getBytes(CharsetUtil.UTF_8));
				//ctx.writeAndFlush(message);
				SynchronousQueue<Object> queue = new SynchronousQueue<>();
				queueMap.put(message.getSessionId(), queue);
				tmp.put(message.getSessionId(),tmpVo);
				channel.writeAndFlush(message);
				return queue;
			}
			if(transferInfo.getMsgTypeEnum()==MsgTypeEnum.UPLOAD){
				tmpVo.setFile(new File(transferInfo.getFilePath()));
				if (!tmpVo.getFile().exists())
					throw new FileNotFoundException(transferInfo.getFilePath());
				RandomAccessFile r = new RandomAccessFile(tmpVo.getFile(), "r");
				tmpVo.setLocalFileChannel(r.getChannel());
				long fileLength = tmpVo.getFile().length();
				int blockCount = (int) (fileLength % ConstantValue.BLOCK_SIZE == 0 ? fileLength / ConstantValue.BLOCK_SIZE : fileLength / ConstantValue.BLOCK_SIZE + 1);
				tmpVo.setBlockCount(blockCount);
				tmp.put(sessionId,tmpVo);
				putMaxFile(1,sessionId,channel);
				SynchronousQueue<Object> queue = new SynchronousQueue<>();
				queueMap.put(sessionId, queue);
				return queue;
			}
		} catch (Exception e) {
			if(null!=tmp.get(sessionId).getLocalFileChannel()){
				tmp.get(sessionId).getLocalFileChannel().close();
			}
			tmp.remove(sessionId);
			SynchronousQueue<Object> queue = queueMap.get(sessionId);
			Message message=new Message();
			message.setCode(-1);
			try {
				queue.put(message);
			} catch (InterruptedException ex) {
				ex.printStackTrace();
			}
			e.printStackTrace();
		}
		return null;
	}
	public void channelInactive(ChannelHandlerContext ctx)   {
		InetSocketAddress address =(InetSocketAddress) ctx.channel().remoteAddress();
		ctx.channel().close();
		//nettyConnManage.removeChannel(ctx.channel());
	}

	/**
	 * @param ctx
	 * @return
	 * @throws Exception
	 * @author fanml
	 * @date 2021/7/4 17:29
	 */
	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		//建立连接的时候发送要下载的文件名
	/*	FileInfo fileInfo = new FileInfo(transferInfo.getFileName(), 0, 0, receiveBlock, null);
		fileInfo.setFilePath(transferInfo.getFilePath());
		fileInfo.setDmzPath(transferInfo.getDmzPath());
		fileInfo.setTargetPath(transferInfo.getTargetPath());
		fileInfo.setFtpAddress(transferInfo.getFtpAddress());
		fileInfo.setFtpUserName(transferInfo.getFtpUserName());
		fileInfo.setFtpPwd(transferInfo.getFtpPwd());
		fileInfo.setFtpPort(transferInfo.getFtpPort());
		String s = JSONObject.toJSONString(fileInfo);
		Message message = new Message("", MsgTypeEnum.DOWNlOAD, s.getBytes(CharsetUtil.UTF_8));
		ctx.writeAndFlush(message);*/
	}

	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

		if (!(msg instanceof Message)) {
			return;
		}
		Message message = null;
		try {
			message = (Message) msg;
			if (MsgTypeEnum.DOWNlOAD == message.getMsgTypeEnum()) {
				//下载文件
				byte[] msgBody = message.getMsgBody();
				FileInfo fileInfo = JSONObject.parseObject(new String(msgBody, CharsetUtil.UTF_8), FileInfo.class);
				int blockIndex = fileInfo.getBlockIndex();//文件块号
				int blockCount = fileInfo.getBlockCount();//文件总块数
				byte[] content = fileInfo.getContent();//文件内容
				String md5Content = fileInfo.getMd5Content();
				String md5FileName=tmp.get(message.getSessionId()).getMd5FileName();
				TransferInfo transferInfo=tmp.get(message.getSessionId()).getTransferInfo();
				if (content != null) {
					if (DigestUtils.md5Hex(content).equals(md5Content)) {
						//校验文件合法性
						System.out.println("文件" + transferInfo.getFileName() + " 下载中" + blockIndex + "/" + blockCount);
						File file = new File(transferInfo.getDmzPath() + md5FileName + File.separator + blockIndex, transferInfo.getFileName()  + "." + String.format("%0" + String.valueOf(blockCount).length() + "d", blockIndex) + ".part");
						fileInfo.setFilePath(transferInfo.getFilePath());
						fileInfo.setDmzPath(transferInfo.getDmzPath());
						fileInfo.setTargetPath(transferInfo.getTargetPath());
						fileInfo.setFtpAddress(transferInfo.getFtpAddress());
						fileInfo.setFtpUserName(transferInfo.getFtpUserName());
						fileInfo.setFtpPwd(transferInfo.getFtpPwd());
						fileInfo.setFtpPort(transferInfo.getFtpPort());
						if (!file.exists()) {
							new File(file.getParent()).mkdirs();
						}
						//保存文件块
						FileChannel channel = new FileOutputStream(file).getChannel();
						ByteBuffer buffer = ByteBuffer.allocate(content.length);
						buffer.put(content);
						buffer.flip();
						channel.write(buffer);
						channel.close();
						buffer.clear();
						tmp.get(message.getSessionId()).setReceiveBlock(blockIndex);
						if (tmp.get(message.getSessionId()).getReceiveBlock() != blockCount) {
							fileInfo.setContent(null);
							fileInfo.setMd5Content(null);
							fileInfo.setBlockIndex(blockIndex + 1);//下次接收的块号
							String s = JSONObject.toJSONString(fileInfo);
							ctx.channel().writeAndFlush(new Message(message.getSessionId(), MsgTypeEnum.DOWNlOAD, s.getBytes(CharsetUtil.UTF_8)));
							return;
						}
						//文件接收完成进行文件合并
						FileUtil.mergeDirFile(transferInfo.getDmzPath()+ md5FileName, tmp.get(message.getSessionId()).getLocalFile().getParent());
						System.out.println("文件下载完成: " + tmp.get(message.getSessionId()).getLocalFile().getPath());
						tmp.remove(message.getSessionId());
						SynchronousQueue<Object> queue = queueMap.get(message.getSessionId());
						queue.put(message);
						//ctx.close();
					} else {
						tmp.remove(message.getSessionId());
						SynchronousQueue<Object> queue = queueMap.get(message.getSessionId());
						message.setCode(-1);
						queue.put(message);
						System.out.println("传输文件已损坏,块号：" + blockIndex);
					}
				}
			}
			if (MsgTypeEnum.UPLOAD == message.getMsgTypeEnum()) {
				byte[] msgBody = message.getMsgBody();
				String s = new String(msgBody, CharsetUtil.UTF_8);
				JSONObject respJson = JSONObject.parseObject(s);
				if (respJson.getInteger("retCode") == 0) {
					//获取服务端返回的块号
					int blockIndex = respJson.getInteger("data").intValue();
					//上传文件块
					putMaxFile(blockIndex,message.getSessionId(),ctx.channel());
				}
			} else if (MsgTypeEnum.RESPONSE == message.getMsgTypeEnum()) {
				if (tmp.get(message.getSessionId()).getBlockIndex() == tmp.get(message.getSessionId()).getBlockCount()) {
					System.out.println("文件: " + tmp.get(message.getSessionId()).getFile().getPath() + "已推送结束");
					tmp.get(message.getSessionId()).getLocalFileChannel().close();
					tmp.remove(message.getSessionId());
					SynchronousQueue<Object> queue = queueMap.get(message.getSessionId());
					queue.put(message);
				}
			}
		} catch (Exception e) {
			if(null!=tmp.get(message.getSessionId()).getLocalFileChannel()){
				tmp.get(message.getSessionId()).getLocalFileChannel().close();
			}
			tmp.remove(message.getSessionId());
			SynchronousQueue<Object> queue = queueMap.get(message.getSessionId());
			message.setCode(-1);
			queue.put(message);
			e.printStackTrace();
		} finally {
			ReferenceCountUtil.release(message);
		}

	}
	private void putMaxFile(int blockIndex,String sessionId,Channel channel) throws IOException {
		tmp.get(sessionId).setBlockIndex(blockIndex);
		int blockCount=tmp.get(sessionId).getBlockCount();
		MappedByteBuffer mappedByteBuffer;
		Message message;
		byte[] fileBlockByte;
		System.out.println("当前推送文件块号: " + blockIndex + "/" + blockCount);
		//文件分块起始位置
		long position = (blockIndex - 1) * ConstantValue.BLOCK_SIZE;
		//文件块长度
		File file=tmp.get(sessionId).getFile();
		FileChannel localFileChannel=tmp.get(sessionId).getLocalFileChannel();
		TransferInfo transferInfo=tmp.get(sessionId).getTransferInfo();
		long size = blockIndex == blockCount ? file.length() - (blockIndex - 1) * ConstantValue.BLOCK_SIZE : ConstantValue.BLOCK_SIZE;
		mappedByteBuffer = localFileChannel.map(FileChannel.MapMode.READ_ONLY, position, size);
		fileBlockByte = new byte[(int) size];
		for (int offset = 0; offset < size; offset++) {
			byte b = mappedByteBuffer.get();
			fileBlockByte[offset] = b;
		}
		FileInfo fileInfo = new FileInfo(transferInfo.getFileName(), file.length(), blockCount, blockIndex, fileBlockByte);
		fileInfo.setDmzPath(transferInfo.getDmzPath());
		fileInfo.setFilePath(transferInfo.getFilePath());
		fileInfo.setTargetPath(transferInfo.getTargetPath());
		fileInfo.setFtpAddress(transferInfo.getFtpAddress());
		fileInfo.setFtpUserName(transferInfo.getFtpUserName());
		fileInfo.setFtpPwd(transferInfo.getFtpPwd());
		fileInfo.setFtpPort(transferInfo.getFtpPort());
		fileInfo.setFtpId(transferInfo.getFtpId());
		String jsonString = JSONObject.toJSONString(fileInfo);
		message = new Message(sessionId, MsgTypeEnum.UPLOAD, jsonString.getBytes(CharsetUtil.UTF_8));
		channel.writeAndFlush(message);
		mappedByteBuffer.clear();
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		System.out.println("发生异常了----------------------------");
		cause.printStackTrace();
		ctx.channel().close();
	}

	@Override
	public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
		//this.ctx = ctx;
	}
}
